﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;

namespace Com.Lancetinc.Evs.Helpers
{
    public static class WpfHelper
    {
        /// <summary>
        ///     是否是设计模式(在设计器里面打开)
        /// </summary>
        public static bool IsInDesignMode => (bool)DesignerProperties.IsInDesignModeProperty
            .GetMetadata(typeof(DependencyObject)).DefaultValue;

        #region FindVisualParent

        public static T FindVisualParent<T>(UIElement element) where T : UIElement
        {
            var parent = element;
            while (parent != null)
            {
                if (parent is T correctlyTyped) return correctlyTyped;

                parent = VisualTreeHelper.GetParent(parent) as UIElement;
            }

            return null;
        }

        #endregion FindVisualParent

        #region GetVisualChild

        public static T GetVisualChild<T>(Visual parent) where T : Visual
        {
            var child = default(T);

            var numVisuals = VisualTreeHelper.GetChildrenCount(parent);
            for (var i = 0; i < numVisuals; i++)
            {
                var v = (Visual)VisualTreeHelper.GetChild(parent, i);
                child = v as T;
                if (child == null) child = GetVisualChild<T>(v);
                if (child != null) break;
            }

            return child;
        }

        public static T GetVisualChild<T>(Visual parent, int index) where T : Visual
        {
            var child = default(T);

            var encounter = 0;
            var queue = new Queue<Visual>();
            queue.Enqueue(parent);
            while (queue.Count > 0)
            {
                var v = queue.Dequeue();
                child = v as T;
                if (child != null)
                {
                    if (encounter == index)
                        break;
                    encounter++;
                }
                else
                {
                    var numVisuals = VisualTreeHelper.GetChildrenCount(v);
                    for (var i = 0; i < numVisuals; i++) queue.Enqueue((Visual)VisualTreeHelper.GetChild(v, i));
                }
            }

            return child;
        }

        public static bool VisualChildExists(Visual parent, DependencyObject visualToFind)
        {
            var queue = new Queue<Visual>();
            queue.Enqueue(parent);
            while (queue.Count > 0)
            {
                var v = queue.Dequeue();
                var child = v as DependencyObject;
                if (child != null)
                {
                    if (child == visualToFind)
                        return true;
                }
                else
                {
                    var numVisuals = VisualTreeHelper.GetChildrenCount(v);
                    for (var i = 0; i < numVisuals; i++) queue.Enqueue((Visual)VisualTreeHelper.GetChild(v, i));
                }
            }

            return false;
        }

        public static IList<T> FindChildren<T>(DependencyObject element) where T : FrameworkElement
        {
            var children = new List<T>();
            for (int counter = 0; counter < VisualTreeHelper.GetChildrenCount(element); counter++)
            {
                if (VisualTreeHelper.GetChild(element, counter) is FrameworkElement ele)
                {
                    if (ele is T correctlyTyped)
                    {
                        children.Add(correctlyTyped);
                    }
                    else
                    {
                        children.AddRange(FindChildren<T>(ele));
                    }
                }
            }
            return children;
        }

        public static IList<FrameworkElement> FindChildren(DependencyObject element, Predicate<FrameworkElement> filter)
        {
            var children = new List<FrameworkElement>();
            for (int counter = 0; counter < VisualTreeHelper.GetChildrenCount(element); counter++)
            {
                if (VisualTreeHelper.GetChild(element, counter) is not FrameworkElement ele)
                    continue;

                if (filter(ele))
                {
                    children.Add(ele);
                }
                children.AddRange(FindChildren(ele, filter));
            }
            return children;
        }

        public static T FindVisualChildByName<T>(Visual parent, string name) where T : Visual
        {
            if (parent != null)
                for (var i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
                {
                    var child = VisualTreeHelper.GetChild(parent, i) as Visual;
                    var controlName = child.GetValue(FrameworkElement.NameProperty) as string;
                    if (controlName == name) return child as T;

                    var result = FindVisualChildByName<T>(child, name);
                    if (result != null)
                        return result;
                }

            return null;
        }
        #endregion GetVisualChild

        public static Action Debounce(this Action func, int milliseconds = 300)
        {
            CancellationTokenSource cancelTokenSource = null;

            return () =>
            {
                cancelTokenSource?.Cancel();
                cancelTokenSource = new CancellationTokenSource();

                Task.Delay(milliseconds, cancelTokenSource.Token)
                    .ContinueWith(t =>
                    {
                        if (t.IsCompleted && !t.IsCanceled)
                        {
                            func();
                        }
                    }, TaskScheduler.Default);
            };
        }

        public static void ExecSafe(Action action)
        {
            var dispatcher = Application.Current.Dispatcher;
            if (dispatcher.CheckAccess())
            {
                action?.Invoke();
            }
            else
            {
                dispatcher.Invoke(action);
            }
        }

        #region ExecLater

        /// <summary>
        /// 延迟一定时间之后执行
        /// </summary>
        /// <param name="a"></param>
        /// <param name="delay"></param>
        /// <param name="priority">执行优先级</param>
        public static IDisposable ExecLater(Action a, int delay = 0, DispatcherPriority priority = DispatcherPriority.ApplicationIdle)
        {
            var dt = new DispatcherTimer(priority, Application.Current.Dispatcher);
            dt.Interval = TimeSpan.FromMilliseconds(delay);
            dt.Tick += dispatcherTimer_Tick;
            dt.Tag = a;
            dt.Start();

            return new DelegateDisposable(() =>
            {
                if (!dt.IsEnabled)
                    return;
                dt.Tick -= dispatcherTimer_Tick;
                dt.Stop();
            });
        }

        private static void dispatcherTimer_Tick(object sender, EventArgs e)
        {
            if (sender is not DispatcherTimer dt)
            {
                return;
            }

            dt.Stop();
            dt.Tick -= dispatcherTimer_Tick;
            (dt.Tag as Action)?.Invoke();
        }
        #endregion

        #region ExecLaterEx

        private static readonly Dictionary<string, DispatcherTimer> ExecTimerDic = new Dictionary<string, DispatcherTimer>();

        /// <summary>
        /// 延迟执行，可以多次执行，后一次执行前会关闭前一次的timer
        /// </summary>
        /// <param name="key">多次执行的时候key必须一致</param>
        /// <param name="a"></param>
        /// <param name="delay"></param>
        public static void ExecLaterEx(string key, Action a, int delay = 0)
        {
            if (!ExecTimerDic.TryGetValue(key, out var dt))
            {
                dt = new DispatcherTimer(DispatcherPriority.ApplicationIdle, Application.Current.Dispatcher);
            }
            else
            {
                dt.Tick -= dispatcherTimer_Tick3;
                if (dt.IsEnabled)
                {
                    Console.WriteLine($"{key} stopped.");
                }
                dt.Stop();
            }
            dt.Interval = TimeSpan.FromMilliseconds(delay);
            dt.Tag = new Tuple<string, Action>(key, a); ;
            dt.Tick += dispatcherTimer_Tick3;
            dt.Start();
            ExecTimerDic[key] = dt;
        }

        private static void dispatcherTimer_Tick3(object sender, EventArgs e)
        {
            if (!(sender is DispatcherTimer dt))
            {
                return;
            }

            var tag = (Tuple<string, Action>)dt.Tag;
            ExecTimerDic.Remove(tag.Item1);
            dt.Stop();
            dt.Tick -= dispatcherTimer_Tick3;
            Console.WriteLine($"{tag.Item1} executed.");
            tag.Item2();
        }

        #endregion
    }
}